/*
 * GraphWindow.cpp
 *
 *  Created on: Apr 10, 2011
 *      Author: Matthew Michelotti
 */
#include <sfv/layout/Layout.h>
#include "GraphWindow.h"
#include "FL/gl.h"
#include "FL/math.h"
#include <utility>
#include <vector>
#include <iostream>
using namespace std;

GraphWindow* GraphWindow::activeWindow = 0;
const Color GraphWindow::edgeColor(0.4f, 0.4f, 0.4f);

GraphWindow::~GraphWindow() {
	delete[] nodeArray;
}

void GraphWindow::draw() {
	if (!valid()) {
		glDisable( GL_LIGHTING);
		glDisable( GL_DITHER);
		glDisable( GL_BLEND);
		glDisable( GL_DEPTH_TEST);

		GLint viewport[4];
		glGetIntegerv(GL_VIEWPORT, viewport);

		glMatrixMode( GL_PROJECTION);
		glLoadIdentity();
		glOrtho(viewport[0], viewport[0] + viewport[2],
				viewport[1] + viewport[3], viewport[1], -1, 1);

		glMatrixMode( GL_MODELVIEW);
		glLoadIdentity();

		glColor3f(1.0f, 1.0f, 1.0f);
		glClearColor(0, 0, 0, 0);
	}

	fillBox(0, 0, 700, 700, Color(1.0f, 1.0f, 1.0f));

	for (uint node1i = 0; node1i < matrix.num_rows; node1i++) {
		VisualNode& node1 = nodeArray[node1i];
		int* neighbors = matrix.getNZCols(node1i);
		int len = matrix.getNNZs(node1i);
		for (int i = 0; i < len; i++) {
			int node2i = neighbors[i];
			if (node2i < (int) node1i)
				continue;
			VisualNode& node2 = nodeArray[node2i];
			drawLine(node1.getPxX(), node1.getPxY(), node2.getPxX(),
					node2.getPxY(), edgeColor);
		}
	}

	for (uint i = 0; i < matrix.num_rows; i++) {
		VisualNode& node = nodeArray[i];
		fillCircle(node.getPxX(), node.getPxY(), getNodeRadius(node.getSize()), node.getColor());
	}
}

double GraphWindow::getNodeRadius(int size) {
	return 3*pow(size, .33);//2*sqrt(size+3);
}

void GraphWindow::fillBox(double x, double y, double w, double h, Color c) {
	glColor3f(c.r, c.g, c.b);

	glBegin( GL_QUADS);
	glVertex2f(x, y);
	glVertex2f(x, y + h);
	glVertex2f(x + w, y + h);
	glVertex2f(x + w, y);
	glEnd();

	glColor3f(1.0f, 1.0f, 1.0f);
}

void GraphWindow::fillCircle(double x, double y, double r, Color c) {
	glColor3f(c.r, c.g, c.b);

	int numEdges = (int) r * .6;
	if (numEdges < 14)
		numEdges = 14;
	double angle = 2.0 * M_PI / numEdges;

	glBegin( GL_TRIANGLE_FAN);
	glVertex2f(x, y);
	for (int i = 0; i <= numEdges; i++) {
		glVertex2f(x + cos(angle * i) * r, y + sin(angle * i) * r);
	}
	glEnd();

	glColor3f(1.0f, 1.0f, 1.0f);
}

void GraphWindow::drawLine(double x1, double y1, double x2, double y2, Color c) {
	glColor3f(c.r, c.g, c.b);

	glBegin( GL_LINES);
	glVertex2f(x1, y1);
	glVertex2f(x2, y2);
	glEnd();

	glColor3f(1.0f, 1.0f, 1.0f);
}

int GraphWindow::handle(int event) {
	switch (event) {
	case FL_PUSH: {
		double myX = Fl::event_x();
		double myY = Fl::event_y();
		for (uint i = 0; i < matrix.num_rows; i++) {
			double diffX = myX - nodeArray[i].getPxX();
			double diffY = myY - nodeArray[i].getPxY();
			double r = getNodeRadius(nodeArray[i].getSize()) + 2;
			if (diffX * diffX + diffY * diffY <= r*r) {
				onClick(i);
				return 1;
			}
		}
		return 1;
	}
	default:
		return Fl_Gl_Window::handle(event);
	}
}

void GraphWindow::forceStep() {
	/*for(uint i = 0; i < matrix.num_rows; i++) {
	 nodeArray[i].stepForce(.002-.004*(rand()%0x1000)/(double)0x1000,
	 .002-.004*(rand()%0x1000)/(double)0x1000);
	 }*/

	sfv::array1d<double> x_pos;
	sfv::array1d<double> y_pos;
	x_pos.resize(matrix.num_rows);
	y_pos.resize(matrix.num_rows);

	for (size_t i = 0; i < matrix.num_rows; i++) {
		x_pos[i] = nodeArray[i].getX();
		y_pos[i] = nodeArray[i].getY();
	}

	sfv::array1d<double> weights;
	size_t no_of_nodes = matrix.num_rows;
	double area = no_of_nodes / 6.0; //no_of_nodes * no_of_nodes;

	double t = no_of_nodes * pow(500 - num_of_redraws / (double) 500, 0.1);

	if (num_of_redraws < 500) {
		sfv::layout::Layout::fruchterman_reingold(matrix, x_pos, y_pos, 1,
				no_of_nodes, area, 0.1, area / no_of_nodes, weights, t);

		double minX = 1000, minY = 1000, maxX = -1000, maxY = -1000;
		for(uint i = 0; i < matrix.num_rows; i++) {
			if(x_pos[i] < minX) minX = x_pos[i];
			if(x_pos[i] > maxX) maxX = x_pos[i];
			if(y_pos[i] < minY) minY = y_pos[i];
			if(y_pos[i] > maxY) maxY = y_pos[i];
		}

		double xOff = .5 - .5*(minX + maxX);
		double yOff = .5 - .5*(minY + maxY);
		if(xOff > .003) xOff = .003;
		else if(xOff < -.003) xOff = -.003;
		if(yOff > .003) yOff = .003;
		else if (yOff < -.003) yOff = -.003;

		for (size_t i = 0; i < matrix.num_rows; i++) {
			x_pos[i] += xOff;
			y_pos[i] += yOff;
			nodeArray[i].setPos(x_pos[i], y_pos[i]);
		}
	}
}

void GraphWindow::onClick(int nodeI) {
	num_of_redraws = 0;
	num_of_clicks++;

	if (hnodes.size() == 0)
		return;
	if (!hnodes[nodeI]->canSplit())
		return;

	map<HNode*, VisualNode*> nodeMap;
	for (uint i = 0; i < hnodes.size(); i++) {
		nodeMap[hnodes[i]] = &nodeArray[i];
	}
	HNode::split(hnodes, nodeI);
	HNode::toMatrix(hnodes, matrix);

	VisualNode* newVisArr = new VisualNode[hnodes.size()];

	for (uint i = 0; i < hnodes.size(); i++) {
		if (nodeMap.count(hnodes[i]) > 0) {
			newVisArr[i] = *nodeMap[hnodes[i]];
		} else {
			newVisArr[i] = nodeArray[nodeI];
			newVisArr[i].setSize(hnodes[i]->getSize());
			newVisArr[i].addNoise();
			if( num_of_clicks == 1 ){
				if( i == 0 ) newVisArr[i].setColor(Color(1.0, 0.0, 0.5));
				if( i == 1 ) newVisArr[i].setColor(Color(0.0, 0.5, 0.8));
				if( i == 2 ) newVisArr[i].setColor(Color(0.3, 0.7, 0.0));
			}
		}
	}
	delete[] nodeArray;
	nodeArray = newVisArr;
}

void GraphWindow::callback(void*) {
	activeWindow->forceStep();
	activeWindow->redraw();
	Fl::repeat_timeout(.05, GraphWindow::callback);
	activeWindow->num_of_redraws++;
}

int GraphWindow::begin(HNode* root) {
	activeWindow = new GraphWindow(0, 0, VisualNode::screenWidth,
			VisualNode::screenHeight, "Graph Window");
	GraphWindow* w = activeWindow;

	w->hnodes.push_back(root);
	HNode::toMatrix(w->hnodes, w->matrix);
	w->nodeArray = new VisualNode[1];
	w->nodeArray[0].setPos(.5, .5);
	w->nodeArray[0].setSize(root->getSize());

	w->show(0, 0);
	Fl::add_timeout(.05, GraphWindow::callback);
	return Fl::run();
}

//int GraphWindow::begin() {
//	GraphWindow* w = new GraphWindow(0, 0, VisualNode::screenWidth, VisualNode::screenHeight, "Graph Window");
//	w->show(0, 0);
//	return Fl::run();
//}

int GraphWindow::begin(const sfv::csr_matrix<int, float>& A,
		const sfv::array1d<double>& x, const sfv::array1d<double>& y) {
	activeWindow = new GraphWindow(0, 0, VisualNode::screenWidth,
			VisualNode::screenHeight, "Graph Window");

	activeWindow->matrix = A;
	activeWindow->nodeArray = new VisualNode[A.num_rows];
	for (uint i = 0; i < A.num_rows; i++) {
		activeWindow->nodeArray[i].setPos(x[i], y[i]);
	}

	activeWindow->show(0, 0);
	Fl::add_timeout(.05, GraphWindow::callback);
	return Fl::run();
}
